/**
 * This file is part of Logisim-evolution.
 *
 * Logisim-evolution is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Logisim-evolution is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Logisim-evolution.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Original code by Carl Burch (http://www.cburch.com), 2011.
 * Subsequent modifications by:
 *   + Haute École Spécialisée Bernoise
 *     http://www.bfh.ch
 *   + Haute École du paysage, d'ingénierie et d'architecture de Genève
 *     http://hepia.hesge.ch/
 *   + Haute École d'Ingénierie et de Gestion du Canton de Vaud
 *     http://www.heig-vd.ch/
 *   + REDS Institute - HEIG-VD, Yverdon-les-Bains, Switzerland
 *     http://reds.heig-vd.ch
 * This version of the project is currently maintained by:
 *   + Kevin Walsh (kwalsh@holycross.edu, http://mathcs.holycross.edu/~kwalsh)
 */

package com.cburch.hex;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;

class Measures {
	private HexEditor hex;
	private int headerChars;
	private int cellChars;
	private int headerWidth;
	private int spacerWidth;
	private int cellWidth;
	private int cellHeight;
	private int cols;
	private int baseX;
	private boolean guessed;

	public Measures(HexEditor hex) {
		this.hex = hex;
		this.guessed = true;
		this.cols = 1;
		this.cellWidth = -1;
		this.cellHeight = -1;
		this.cellChars = 2;
		this.headerChars = 4;

		computeCellSize(null);
	}

	private void computeCellSize(Graphics g) {
		HexModel model = hex.getModel();

		// compute number of characters in headers and cells
		if (model == null) {
			headerChars = 4;
			cellChars = 2;
		} else {
			int logSize = 0;
			long addrEnd = model.getLastOffset();
			while (addrEnd > (1L << logSize)) {
				logSize++;
			}
			headerChars = (logSize + 3) / 4;
			cellChars = (model.getValueWidth() + 3) / 4;
		}

		// compute character sizes
		FontMetrics fm = g == null ? null : g.getFontMetrics(hex.getFont());
		int charWidth;
		int spaceWidth;
		int lineHeight;
		if (fm == null) {
			charWidth = 8;
			spaceWidth = 6;
			Font font = hex.getFont();
			if (font == null) {
				lineHeight = 16;
			} else {
				lineHeight = font.getSize();
			}
		} else {
			guessed = false;
			charWidth = 0;
			for (int i = 0; i < 16; i++) {
				int width = fm.stringWidth(Integer.toHexString(i));
				if (width > charWidth)
					charWidth = width;
			}
			spaceWidth = fm.stringWidth(" ");
			lineHeight = fm.getHeight();
		}

		// update header and cell dimensions
		headerWidth = headerChars * charWidth + spaceWidth;
		spacerWidth = spaceWidth;
		cellWidth = cellChars * charWidth + spaceWidth;
		cellHeight = lineHeight;

		// compute preferred size
		int width = headerWidth + cols * cellWidth + (cols / 4) * spacerWidth;
		long height;
		if (model == null) {
			height = 16 * cellHeight;
		} else {
			long addr0 = getBaseAddress(model);
			long addr1 = model.getLastOffset();
			long rows = (int) (((addr1 - addr0 + 1) + cols - 1) / cols);
			height = rows * cellHeight;
			if (height > Integer.MAX_VALUE)
				height = Integer.MAX_VALUE;
		}

		// update preferred size
		Dimension pref = hex.getPreferredSize();
		if (pref.width != width || pref.height != height) {
			pref.width = width;
			pref.height = (int) height;
			hex.setPreferredSize(pref);
			hex.revalidate();
		}

		widthChanged();
	}

	void ensureComputed(Graphics g) {
		if (guessed || cellWidth < 0)
			computeCellSize(g);
	}

	public long getBaseAddress(HexModel model) {
		if (model == null) {
			return 0;
		} else {
			long addr0 = model.getFirstOffset();
			return addr0 - addr0 % cols;
		}
	}

	public int getBaseX() {
		return baseX;
	}

	public int getCellChars() {
		return cellChars;
	}

	public int getCellHeight() {
		return cellHeight;
	}

	public int getCellWidth() {
		return cellWidth;
	}

	public int getColumnCount() {
		return cols;
	}

	public int getLabelChars() {
		return headerChars;
	}

	public int getLabelWidth() {
		return headerWidth;
	}

	public int getValuesWidth() {
		return ((cols - 1) / 4) * spacerWidth + cols * cellWidth;
	}

	public int getValuesX() {
		return baseX + spacerWidth;
	}

	void recompute() {
		computeCellSize(hex.getGraphics());
	}

	public long toAddress(int x, int y) {
		HexModel model = hex.getModel();
		if (model == null)
			return Integer.MIN_VALUE;
		long addr0 = model.getFirstOffset();
		long addr1 = model.getLastOffset();

		long base = getBaseAddress(model) + ((long) y / cellHeight) * cols;
		int offs = (x - baseX) / (cellWidth + (spacerWidth + 2) / 4);
		if (offs < 0)
			offs = 0;
		if (offs >= cols)
			offs = cols - 1;

		long ret = base + offs;
		if (ret > addr1)
			ret = addr1;
		if (ret < addr0)
			ret = addr0;
		return ret;
	}

	public int toX(long addr) {
		int col = (int) (addr % cols);
		return baseX + (1 + (col / 4)) * spacerWidth + col * cellWidth;
	}

	public int toY(long addr) {
		long row = (addr - getBaseAddress(hex.getModel())) / cols;
		long ret = row * cellHeight;
		return ret < Integer.MAX_VALUE ? (int) ret : Integer.MAX_VALUE;
	}

	void widthChanged() {
		int oldCols = cols;
		int width;
		if (guessed || cellWidth < 0) {
			cols = 16;
			width = hex.getPreferredSize().width;
		} else {
			width = hex.getWidth();
			int ret = (width - headerWidth)
					/ (cellWidth + (spacerWidth + 3) / 4);
			if (ret >= 16)
				cols = 16;
			else if (ret >= 8)
				cols = 8;
			else
				cols = 4;
		}
		int lineWidth = headerWidth + cols * cellWidth + ((cols / 4) - 1)
				* spacerWidth;
		int newBase = headerWidth + Math.max(0, (width - lineWidth) / 2);
		if (baseX != newBase) {
			baseX = newBase;
			hex.repaint();
		}
		if (cols != oldCols)
			recompute();
	}
}
